package com.cityu.bac.rfidDriver;

import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import android.app.Activity;
import android.content.Context;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

public class RfidDriver extends CommandProcess {
	
	private static final String TAG = "RfidDriver";
	    
	private DeviceListener deviceListener = null;
	private GetNotiThread getNotiThread = null;
	private NotiEventHandler handler = null;
	private NotiEventCallback callback = null;
	
	private int status = NONE;

	static int NONE = -1;
    static int DISCONNECT = 0;
	static int CONNECTED = 1;
	
	
	public static int EVENT_DISCONNECT = 0;
	public static int EVENT_CONNECTED = 1;
	public static int EVENT_DATA = 2;
	
	public enum DeviceType{
	    UsbReader,
	    MiniReader,  
	};
	
	
	/**
	 * Get the type of the connected reader.
	 * @return
	 */
	public DeviceType getDeviceType(){
	    return deviceType;
	}
	
	
	/**
	 * Get the name of the device, if the device is bluetooth rfid reader, return the bluetooth mac address.
	 * @return the name of the device
	 */
	public String getDeviceName(){
		return serialPort.getName();
	}
	
	@SuppressWarnings({
            "unchecked", "rawtypes"
    })
    private static HashMap<String,RfidDriver> driverMap = new HashMap();
	
	
	private byte[] cmdResp = null;
	private Object cmdLock = new Object();
	private byte currentCmdType;

	int refNum = 0;
	private String devicePath = "";
	private int baudrate = 115200;
	
	private File deviceFile = null;
	
	public static Context context;
	
	boolean enableSound = true;;
	MediaPlayer player;
	private int soundResourse = 0;
	
	/**
	 * check if the sound is enable
	 * @return If the sound is enable
	 */
	public boolean isEnableSound() {
		return enableSound;
	}

	/**
	 * enable or disable the sound
	 * @param enableSound 
	 */
	public void setEnableSound(boolean enableSound) {
		this.enableSound = enableSound;
	}
	
	public void setSoundResourse(int res) {
		this.soundResourse = res;
	}

	private RfidDriver(String devicePath, int baudrate, Context context){
//		super(devicePath, baudrate);
		
		this.devicePath = devicePath;
		this.baudrate = baudrate;
		
		if(context instanceof Activity){
			this.context = ((Activity)context).getApplicationContext();
		}else{
			this.context = context;
		}
		
		deviceListener = new DeviceListener(this);
		deviceListener.start();
		
		handler = new NotiEventHandler(this);
		getNotiThread = new GetNotiThread(this,handler);
		getNotiThread.start();
		
	}
	
	public void playSound(){
		if(player == null){
			try {
				player = MediaPlayer.create(context, soundResourse);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		if(player != null){
			try{
				player.start();
				player.setVolume(100, 100);
			}catch(Exception e){}
		}
	}
	
	
	
	int getStatus() {
        return status;
    }

    void setStatus(int status) {
        if(this.status == status)
            return;
        this.status = status;
        
    }
	
	int getBaudrate() {
        return baudrate;
    }

    void setBaudrate(int baudrate) {
        this.baudrate = baudrate;
    }

    File getDeviceFile() {
        return deviceFile;
    }

    void setDeviceFile(File deviceFile) {
        this.deviceFile = deviceFile;
    }



    /**
	 * Allocate a RFID driver, Don't forget to call free() to free the driver.
	 * @param devicePath the path of the device, example: 
	 * 				"bluetooth:11:22:33:44:55:66" 	will connect the bluetooth rfid reader which MAC address is  11:22:33:44:55:66, 
	 * 				"bluetooth:" 					will auto find the bluetooth rfid reader and connect
	 * @param baudrate the baud rate of the serial port, eg. 115200
	 * @return a RFID driver
	 */
	public static RfidDriver allocDriver(String devicePath, int baudrate, Context context) {
	    RfidDriver driver = driverMap.get(devicePath.startsWith("bluetooth") ? "bluetooth" :devicePath);
	    if(driver == null){
	        driver = new RfidDriver(devicePath, baudrate, context);
	        driverMap.put(devicePath.startsWith("bluetooth") ? "bluetooth" :devicePath , driver);
	    }
	    driver.refNum++;
	    Log.d(TAG, "refNum = "+driver.refNum);
        return driver;
	}
	
	/**
	 * Free the RFID driver. 
	 */
	public void free(){
	    refNum--;
	    Log.d(TAG, "refNum = "+refNum);
	    if(refNum < 0 || refNum == 0){
	        refNum = 0;
	        try {
                stopRead();
            } catch (Exception e) {
                e.printStackTrace();
            }
	        super.close();
	        driverMap.remove(devicePath.startsWith("bluetooth") ? "bluetooth" :devicePath);
	        if(getNotiThread != null){
	            getNotiThread.setStop(true);
	            getNotiThread.interrupt();
//	            handler = null;
	            getNotiThread = null;
	        }
	        if(deviceListener != null){
	            deviceListener.setStop(true);
	            deviceListener.interrupt();
                deviceListener = null;
            }
	        if(player != null)
	        	player.release();
	    }
	} 
	
	/**
	 * Set the notification event callback.
	 * @param callback a class which implements RfidDriver.NotiEventCallback.
	 */
	public void setNotiEventCallback(NotiEventCallback callback){
	    this.callback = callback;
	}
	
	protected synchronized byte[] sendCommand(byte[] data,long timeout) throws Exception{
	    pktProcess.sendCommand(data);
        currentCmdType = data[2];
        cmdResp = null;
        synchronized(cmdLock){
            cmdLock.wait(timeout);
        }
        if(cmdResp == null)
            throw new Exception("get null resp");
        return cmdResp;
	}
	protected byte[] sendCommand(byte[] data) throws Exception{
	    return sendCommand(data,500);
    }
	
    public void stopRead() throws Exception{
    	super.stopRead();
    	if(getNotiThread != null)
    		getNotiThread.clearDatalist();
    }

	
	
	private void handleNotiEvent(int eventType,Object obj){
	    
	   if(eventType == EVENT_DATA && enableSound){
	    	playSound();
	   }
	   
	   if(callback != null){
	        callback.handleNotiEvent(eventType, obj);
	   }
    }
	
	
	//--------------------inner class---------------------------------
	
	public interface NotiEventCallback{
	    /**
	     * When the RFID driver have read some tag, the driver will call this method.
	     * @param eventType event type
	     * @param obj data, eg. ArrayList< byte[] > of tags.
	     */
		public abstract void handleNotiEvent(int eventType,Object obj);
	}
	
	class NotiEventHandler extends Handler{
		
		private RfidDriver driver = null;
		
		public NotiEventHandler(Looper looper) {
			super(looper);
		}
		public NotiEventHandler(RfidDriver driver){
			super(Looper.myLooper());
			this.driver = driver;
		}
		
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			driver.handleNotiEvent(msg.what, msg.obj);
		}
	}
	
	
	class GetNotiThread extends Thread{
//		private static final String TAG = "GetNotiThread";
		
		private boolean stop = false;
		private RfidDriver driver = null;
		private NotiEventHandler handler = null;
		
		public static final int STOP = 0;
		public static final int RUN = 1;
		public static final int PAUSE = 2;
		
		private int istate = STOP;
		
		private ArrayList<byte[]> datalist = new ArrayList<byte[]>();
		
		private long preSendNotfTime = System.currentTimeMillis();
		
		
		public void clearDatalist(){
			datalist = new ArrayList<byte[]>();
		}
		
		public GetNotiThread(RfidDriver driver, NotiEventHandler handler){
			super();
			this.driver = driver;
			this.handler = handler;
		}
		 
		public int getThreadState(){
			return istate;
		}
		
		public void setThreadState(int state){
			istate = state;
//			Log.d(TAG, "Thread state = " + state);
		}
		
		public void setStop(boolean stop){ 
			this.stop = stop;
		}

		public void run() {
			try{
				while(!stop){
	
					setThreadState(RUN);
					if(pktProcess == null){
					    try {
	                        Thread.sleep(200);
	                    } catch (InterruptedException e) {
//	                        e.printStackTrace();
	                    }
					    continue;
					}
					int dataType = -1;
					try{
						dataType = driver.pktProcess.getRxDateType();
					}catch(Exception e){
//						e.printStackTrace();
					}
					
					if(System.currentTimeMillis() - preSendNotfTime > 300){
	                    sendNotify();
	                }
					
					if(dataType == -1){
					    sendNotify();
					    continue;
					}else if(dataType == -2){
						 if(driver.getStatus() != DISCONNECT){
	 	                    driver.setStatus(DISCONNECT);
	 	                    driver.close();
	 	                    try{
		 	                    if(handler != null){
		 	                    	Message msg = driver.handler.obtainMessage(EVENT_DISCONNECT,null);
		 	                    	driver.handler.sendMessage(msg);
		 	                    }
	 	                    }catch(Exception e){
//	 	                    	e.printStackTrace();
	 	                    }
						 }
						 continue;
					}
							
					if(dataType == Command.typeNotify){
					    doNotify();
					}else if(dataType == Command.typeResp){
					    doCmdRsp();
					}
					
				}
				setThreadState(STOP);
			}catch(Exception e){}
			
		}
		
		private void doNotify(){
		    
            try {
                byte[] data = driver.pktProcess.getNoti(300);
                if(data != null){
                    datalist.add(data);
                }
                
            } catch (Exception e) {
                e.printStackTrace();
//                Message msg = handler.obtainMessage(NotiEventHandler.NOTI_FAIL,null);
//                handler.sendMessage(msg);
            }
		}
		
		private void sendNotify(){
		    if(datalist.size() > 0){
		        preSendNotfTime = System.currentTimeMillis();
		        try{
			        if(handler != null){
			        	Message msg = handler.obtainMessage(EVENT_DATA,datalist);
			        	handler.sendMessage(msg);
			        }
		        }catch(Exception e){
//		        	e.printStackTrace();
		        }
		        datalist = new ArrayList<byte[]>();
		    }
		}
		
		private void doCmdRsp(){
		    Log.d(TAG, "doCmdRsp");
		    try {
		        driver.cmdResp = driver.pktProcess.getResponse();
            } catch (Exception e) {
//                e.printStackTrace();
            }
		    if(driver.cmdResp == null)
		    	return;
		    else if(driver.cmdResp[2] == driver.currentCmdType || Command.isExtResp(driver.currentCmdType, driver.cmdResp) || driver.cmdResp[2] == (byte)0xFF){
                synchronized(driver.cmdLock){
                    driver.cmdLock.notifyAll();
                }
            }else{
                driver.cmdResp = null;
                return;
            }
            
		}
		
	}
	
	
	class DeviceListener extends Thread{
	    RfidDriver driver = null;
	    private boolean stop = false;
	    private int disconTimes = 0;
	    
	    public DeviceListener(RfidDriver driver){
	        this.driver = driver;
	    }
	    
	    public void setStop(boolean stop){ 
            this.stop = stop;
        }
	    public void loadDriver(){
	        Process p;
	        try {
	           // Preform su to get root privledges
	           p = Runtime.getRuntime().exec("su"); 

	           // Attempt to write a file to a root-only
	           DataOutputStream os = new DataOutputStream(p.getOutputStream());
	           os.writeBytes("insmod /data/data/com.cityu.bac/lib/libusbserial.so\n");
	           os.writeBytes("insmod /data/data/com.cityu.bac/lib/libftdi_sio.so\n");
	           os.flush();
	           try {
	               Thread.sleep(200);
    	       } catch (InterruptedException e1) {
//    	           e1.printStackTrace();
    	       }
	           os.writeBytes("chmod 666 /dev/ttyUSB*\n");

	           os.writeBytes("exit\n");
	           os.flush();
	           try {
	              p.waitFor();
	                   if (p.exitValue() != 255) { //success
	                   }
	                   else {
	                   }
	           } catch (InterruptedException e) {
//	                       e.printStackTrace();
	           }
	        } catch (IOException e) {
//	                       e.printStackTrace();
	        }
	    }
	    
	    private void changeFileMod(){
	        Process p;
            try {
               // Preform su to get root privledges
               p = Runtime.getRuntime().exec("su"); 

               // Attempt to write a file to a root-only
               DataOutputStream os = new DataOutputStream(p.getOutputStream());

               os.writeBytes("chmod 666 /dev/ttyUSB*\n");

               os.writeBytes("exit\n");
               os.flush();
               try {
                  p.waitFor();
                       if (p.exitValue() != 255) { //success
                       }
                       else {
                       }
               } catch (InterruptedException e) {
//                           e.printStackTrace();
               }
            } catch (IOException e) {
//                           e.printStackTrace();
            }
	    }
	    
	    private  boolean getDriver(String prefix) throws Exception{
	        SerialPort serialPort = null;
	        PacketProcess pktProcess = null;
	        
//	        File f=new File("/dev/");
//	        File[] files=f.listFiles();
//	        if(files == null)
//	            return false;
	        changeFileMod();
	        for(int i = 0;i < 10;i++){
	            File file = new File(prefix + i);
	            if(file.exists() || prefix.startsWith("bluetooth")){
	                if(prefix.startsWith("bluetooth")){
	                	i = 10;
	                	try{
	                    serialPort = new BluetoothSerialPort(prefix.substring("bluetooth".length()+1));
	                	}catch(Exception e){
	                		continue;
	                	}
	                }else{
    	                try{
    	                serialPort = new SerialPort(file, driver.getBaudrate());
    	                }catch(Exception e){
                            Log.d(TAG, "test "+ file.toString()+"fail!");
                            continue;
                        }
	                }
	                pktProcess = new PacketProcess(driver,serialPort);
	                driver.setSerialPort(serialPort);
	                driver.setPktProcess(pktProcess);
	                
	                //test driver
	                for(int j = 0;j < 3;j++){
	                    try{
	                        driver.stopRead();
	                    }catch(Exception e){
	                        Log.d(TAG, "test "+ file.toString()+"fail!");
	                        serialPort.close();
	                        pktProcess.stopReadBytes(true);
	                        continue;
	                    }
	                    Log.d(TAG, "test "+ file.toString()+"OK!");
	                    driver.setDeviceFile(file);
	                    
//	                    //what device type
//	                    deviceType = DeviceType.MiniReader;
//	                    try{
//	                        driver.releaseButton();
//	                    }catch(Exception e){
//	                        deviceType = DeviceType.UsbReader;
//	                    }
	                    //what device type
/*	                    for(int k = 0;k < 2;k++){
	                        deviceType = DeviceType.MiniReader;
	                        try{
	                            driver.releaseButton();
	                        }catch(Exception e){
	                            if(e.getMessage().equals("get null resp"))
	                                continue;
	                            else
	                                deviceType = DeviceType.UsbReader;
	                        }
	                        break;
	                    }
*/
	                    
	                    deviceType = DeviceType.UsbReader;
		                for(int k = 0;k < 4;k++){
		                	
		                    try{
		                        driver.releaseButton();
		                    }catch(Exception e){
		                    	if(e.getMessage() == null || e.getMessage().equals("get null resp"))
		                    		continue;	  
		                    	else
		                    		break;
		                    }
		                    deviceType = DeviceType.MiniReader;
		                    break;
		                }
	                        	                    
	                    return true;
	                }
	            }
	        }
	        return false;
	    }
	    
	    public void run(){
	        Looper.prepare();
	        loadDriver();
	        
	        while(!stop){
	            
	            if(driver.getStatus() == DISCONNECT || driver.getStatus() == NONE){
	                try {
	                    String prefix = "";
	                    if(driver.devicePath.equals("usb"))
	                        prefix = "/dev/ttyUSB";
	                    else if(driver.devicePath.equals("serial"))
	                        prefix = "/dev/ttySAC";
	                    else
	                        prefix = driver.devicePath;
                        if(getDriver(prefix)){
                            if(driver.getStatus() != CONNECTED){
                                driver.setStatus(CONNECTED);
                                try{
	        	                    if(driver.handler != null){
	        	                    	Message msg = driver.handler.obtainMessage(EVENT_CONNECTED,null);
	        	                    	driver.handler.sendMessage(msg);
	        	                    }
                                }catch(Exception e){
                                	e.printStackTrace();
                                }
                            }
                        }else if(driver.getStatus() == NONE){
                            disconTimes++;
                            if(disconTimes >= 2){
                                disconTimes = 0;
                                driver.setStatus(DISCONNECT);
                                try{
	        	                    if(driver.handler != null){
	        	                    	Message msg = driver.handler.obtainMessage(EVENT_DISCONNECT,null);
	        	                    	driver.handler.sendMessage(msg);
	        	                    }
                                }catch(Exception e){
                                	e.printStackTrace();
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
	            }else if(driver.getStatus() == CONNECTED){
	                if(driver.devicePath.equals("usb")){
    	                File file = new File("/sys/bus/usb/devices/1-1");
    	                File file2 = new File("/sys/bus/usb/devices/2-1");
    	                if(!file.exists() && !file2.exists()){
    	                    if(driver.getStatus() != DISCONNECT){
        	                    driver.setStatus(DISCONNECT);
        	                    driver.close();
        	                    try{
	        	                    if(driver.handler != null){
	        	                    	Message msg = driver.handler.obtainMessage(EVENT_DISCONNECT,null);
	        	                    	driver.handler.sendMessage(msg);
	        	                    }
        	                    }catch(Exception e){
        	                    	e.printStackTrace();
        	                    }
    	                    }
    	                }
	                }
	            }
	            
	            
	            try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
//                    e.printStackTrace();
                }
	        }
	    }
	}

}
